<?php
/**
 * 基础视图类
 *
 * @author Mo yi <root@imoi.cn>
 * @link   http://www.imoi.cn
 * @copyright Copyright &copy; 2010-2012 DuoLam Software LLC
 * @license http://www.imoi.cn/license/
 * @version $Id: dpView.php 2799 2012-04-04 09:31:13Z Mo.yi $
 * @package view
 * @since 1.0
 */

class dpView extends dpComponent
{

	/**
	 * 视图载入文件目录
	 */
	private $_loadFilePath;

	/**
	 * 配置信息
	 */
	protected $config;

	/**
	 * 注入视图的变量
	 */
	private $_var = array();

	/**
	 * 视图标签
	 */
	private $_tag = array();

	/**
	 * 是否使用主题
	 */
	public $isTheme  = false;

	/**
	 * 是否使用布局
	 */
	public $isLayout = false;

	/**
	 * 布局名称
	 */
	public $layout;

	/**
	 * 视图目录
	 */
	private $_viewPath;

	/**
	 * 主题目录名
	 */
	protected $themePath;

	/**
	 * 构造方法
	 */
	public function __construct()
	{
		$this->config = $this->setConfig('views');
		$this->mkViewDir(D::getPathAlias('runtime').DS.'views'.DS);
		$this->mkViewDir(D::getPathAlias('runtime').DS.'views'.DS.'layout'.DS);
	}

	/**
	 * 获取当前app配置的视图信息
	 */
	public function getConfig()
	{
		return $this->config;
	}

	/**
	 * 激活一个主题，一个应用只能激活一个主题
	 * @param $themeName 主题名
	 */
	public function setTheme($themeName)
	{
		$this->config['default_theme'] = $themeName;
	}

	/**
	 * 返回当前设置的主题
	 * @return string
	 */
	public function getTheme()
	{
		return $this->config['default_theme'];
	}

	/**
	 * 设置视图扩展名后缀
	 * @param string $suffix
	 */
	public function setSuffix($suffix)
	{
		$this->config['view_suffix'] = $suffix;
	}

	/**
	 * 返回视图扩展名后缀
	 * @return string
	 */
	public function getSuffix()
	{
		return isset($this->config['view_suffix']) ? $this->config['view_suffix'] : '.php';
	}

	/**
	 * 设置控制视图根目录
	 * @param string $path
	 */
	public function setCtrlViewPath($path)
	{
		$this->config['view_ctrl_path'] = $path;
	}
	
	/**
	 * 返回控制器根目录
	 * @return string
	 */
	public function getCtrlViewPath()
	{
		return $this->config['view_ctrl_path'];
	}

	/**
	 * 设置文件载入的基础目录
	 * @param string $path 视图根目录
	 */
	public function setLoadFilePath($path)
	{
		$this->_loadFilePath = $path;
	}

	/**
	 * 返回文件载入的基础目录
	 * @return string
	 */
	public function getLoadFilePath()
	{
		return $this->_loadFilePath;
	}

	/**
	 * 设置视图根目录
	 * @param string $path 视图根目录地址
	 */
	public function setViewPath($path)
	{
		$this->_viewPath = $path;
	}

	/**
	 * 获取视图根目录
	 * @return string 视图根目录地址
	 */
	public function getViewPath()
	{
		return $this->_viewPath;
	}

	/**
	 * 设置主题目录
	 */
	public function setThemePath($path)
	{
		$this->themePath = $path;
	}

	/**
	 * 获取主题目录名
	 */
	public function getThemePathName()
	{
		return empty($this->themePath) ? 'themes' : $this->themePath;
	}

	/**
	 * 返回当前激活后的主题基础目录
	 * @return boolean or string 主题地址
	 */
	public function getThemePath()
	{
		if ($this->isTheme && $this->getTheme()) {
			$theme = APP_PATH . DS . $this->getThemePathName() . DS . $this->getTheme() . DS;
			
			if (is_dir($theme))
				return $theme;
			else
				dpException::error('使用的主题不存在：'.$this->getTheme());
		} else return false;
	}

	/**
	 * 返回布局名称，带绝对地址
	 * @return boolean or string 布局文件地址
	 */
	public function getLayout()
	{
		if($this->isLayout && !empty($this->layout)) {
			$layout = $this->getViewPath() . 'layout' . DS . $this->layout . $this->getSuffix();
			return is_file($layout) ? $layout : dpException::error('布局文件不存在：'.$layout);
		} else return false;
	}

	/**
	 * 获取视图编译内容
	 * @param  string $file 视图文件名，带路径
	 * @return string html代码
	 */
	protected function getCompileFile($file)
	{
		$cachefile  = D::getPathAlias('runtime').DS.'views'.DS.md5($file).'.php';
		$layoutfile = D::getPathAlias('runtime').DS.'views'.DS.'layout'.DS.md5($this->getLayout()).'.php';
		extract($this->_var, EXTR_OVERWRITE);

		if ($this->getLayout()) {
			$ob_file = $layoutfile;
			ob_start();
			require $cachefile;
			$cacheContent = ob_get_contents();
			ob_end_clean();
			$layout_content = &$cacheContent;
		} else $ob_file = $cachefile;

		ob_start();
		require $ob_file;
		$content = ob_get_contents();
		ob_end_clean();

		return $content;
	}

	/**
	 * 检查视图是否改动
	 * @param string $file 视图文件名，带路径
	 * @return boolean
	 */
	protected function checkFile($file)
	{
		$cachefile  = D::getPathAlias('runtime').DS.'views'.DS.md5($file).'.php';
		$layoutfile = D::getPathAlias('runtime').DS.'views'.DS.'layout'.DS.md5($this->getLayout()).'.php';
		if (is_file($cachefile)) {
			$is = @filemtime($file) != @filemtime($cachefile) ? false : true;

			if ($this->getLayout()) {
				if (is_file($layoutfile))
					$lis = @filemtime($this->getLayout()) !=  @filemtime($layoutfile) ? false : true;
				else
					$lis = false;
			} else $lis = true;

			return $is && $lis ? true : false;
		} else return false;
	}

	/**
	 * 编译视图文件
	 * 1.注入变量
	 * 2.解析自定义标签
	 * 3.返回编译后的视图内容：包含PHP的视图代码
	 * @param string  $file 视图文件名，带路径
	 * @return string 视图代码
	 */
	protected function compile($file)
	{
		$layout  = $this->getLayout() ? file_get_contents($this->getLayout()) : null;

		if ($layout)
			$layout = empty($this->_tag) ? $layout : preg_replace($this->_tag['regular'],$this->_tag['string'],$layout);

		$content = file_get_contents($file);
		$content = empty($this->_tag) ? $content : preg_replace($this->_tag['regular'],$this->_tag['string'],$content);
		return $this->cacheViewFile($file,$content,$layout);
	}

	/**
	 * 对编译后的视图文件进行缓存
	 * @param string $file 视图文件名，带路径
	 * @param string $content 编译后的视图内容
	 * @param string $layout  编译后的布局内容
	 * @return string 缓存后的视图代码
	 */
	protected function cacheViewFile($file,$content,$layout = '')
	{
		$cachefile  = D::getPathAlias('runtime').DS.'views'.DS.md5($file).'.php';
		@file_put_contents($cachefile, $content);
		@touch($cachefile, @filemtime($file));

		if ($layout) {
			$layoutfile = D::getPathAlias('runtime').DS.'views'.DS.'layout'.DS.md5($this->getLayout()).'.php';
			@file_put_contents($layoutfile, $layout);
			@touch($layoutfile, @filemtime($this->getLayout()));
		}

		return $this->getCompileFile($file);
	}

	/**
	 * 向模板添加自定义标签
	 * @param string 正则表达式
	 * @param string 替换的内容
	 */
	public function addTag($regular,$string)
	{
		$this->_tag['regular'][] = $regular;
		$this->_tag['string'][]  = $string;
	}

	/**
	 * 获取已自定义的标签
	 * @return array 自定义标签列表
	 */
	public function getTag()
	{
		return $this->_tag;
	}

	/**
	 * 向视图注入变量
	 * @param string or array 	变量名
	 * @param string $value		变量值
	 */
	public function assign($var, $value = '')
	{
		if (is_array($var)) {
			foreach($var as $v => $k) $this->_var[$v] = $k;
		} else $this->_var[$var] = $value;
	}

	/**
	 * 获取向模板注入的变量
	 * @return array
	 */
	public function getViewVar()
	{
		return $this->_var;
	}

	/**
	 * 视图渲染
	 * @param string  $file 视图文件名，带路径
	 * @param array   $var  向模板注入变量
	 * @return string $content html代码
	 */
	public function render($file, $var)
	{
		is_file($file) or dpException::error('视图文件不存在：'.$file);
		$this->assign($var);
		if ($this->checkFile($file))
			return $this->getCompileFile($file);
		else
			return $this->compile($file);
	}

	/**
	 * 建立文件夹，并尝试设置为可写
	 * @param string $dir 文件夹名，可以带地址
	 */
	private function mkViewDir($dir)
	{
		if (!is_dir($dir))
			@mkdir($dir,0755,true) or dpException::error('视图编译缓存目录建立失败，请手动建立：'.$dir);
	}

	/**
	 * 添加系统内置标签
	 */
	protected function systemTags()
	{
		$this->addTag('/\{__URL__\}/i',  $this->getLoadFilePath());
		$this->addTag('/\{__ROOT__\}/i', ROOT);
		$this->addTag('/\{__PUBLIC__\}/i', ROOT . 'public/');
		$this->addTag('/\{T_(\w+)\}/', '<?php echo $1;?>');

		$this->addTag('/\{cu=(.*?)\}/i','<?php echo D::createUrl("\\1"); ?>');
		$this->addTag('/\{cup=(.*?),(.*?)/i','<?php echo D::createUrl("\\1", trim("\\2")); ?>');

		$this->addTag('/\{foreach\s+(\S+)\s+(\S+)\}\s*/i','<?php \$i=0;if(!empty($1) && is_array($1)) foreach($1 AS $2) { \$i++;  ?>');
		$this->addTag('/\{foreach\s+(\S+)\s+(\S+)\s+(\S+)\}\s*/i','<?php \$i=0;if(!empty($1) && is_array($1)) foreach($1 AS $2 => $3) { \$i++;  ?>');
		$this->addTag('/\{\/foreach\}\s*/iU','<?php } ?>');

		$this->addTag('/\{if\s+(.+?)\}/i','<?php if($1) { ?>');
		$this->addTag('/\{elseif\s+(.+?)\}/i','<?php } else if($1) { ?>');
		$this->addTag('/\{else\}/i','<?php } else { ?>');
		$this->addTag('/\{\/if\}/i','<?php } ?>');

		$this->addTag('/\{(\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/','<?php if(isset(\\1)) echo \\1;?>');
		$this->addTag('/\{(\$[a-z0-9_]+)\.([a-z0-9_]+)\}/i','<?php if(isset(\\1[\'\\2\'])) echo \\1[\'\\2\'];?>');
		$this->addTag('/\{(\$[a-z0-9_]+)\.(\$[a-z0-9_]+)\.([a-z0-9_]+)\}/i','<?php if(isset(\\1[\\2][\'\\3\'])) echo \\1[\\2][\'\\3\'];?>');
		$this->addTag('/\{include\sfile=(.*?)\}/i','<?php $this->layout=null; $this->render("\\1"); ?>');
		
	}
}